#include "zgl.h"
#include <string.h>

void glopNormal(GLContext* c, GLParam* p) {
  V3 v;

  v.X = p[1].f;
  v.Y = p[2].f;
  v.Z = p[3].f;

  c->current_normal.X = v.X;
  c->current_normal.Y = v.Y;
  c->current_normal.Z = v.Z;
  c->current_normal.W = 0;
}

void glopTexCoord(GLContext* c, GLParam* p) {
  c->current_tex_coord.X = p[1].f;
  c->current_tex_coord.Y = p[2].f;
  c->current_tex_coord.Z = p[3].f;
  c->current_tex_coord.W = p[4].f;
}

void glopEdgeFlag(GLContext* c, GLParam* p) {
  c->current_edge_flag = p[1].i;
}

void glopColor(GLContext* c, GLParam* p) {
  c->current_color.X = p[1].f;
  c->current_color.Y = p[2].f;
  c->current_color.Z = p[3].f;
  c->current_color.W = p[4].f;
  c->longcurrent_color[0] = p[5].ui;
  c->longcurrent_color[1] = p[6].ui;
  c->longcurrent_color[2] = p[7].ui;

  if (c->color_material_enabled) {
    GLParam q[7];
    q[0].op = OP_Material;
    q[1].i = c->current_color_material_mode;
    q[2].i = c->current_color_material_type;
    q[3].f = p[1].f;
    q[4].f = p[2].f;
    q[5].f = p[3].f;
    q[6].f = p[4].f;
    glopMaterial(c, q);
  }
}

void gl_eval_viewport(GLContext* c) {
  GLViewport* v;
  float zsize = (1 << (ZB_Z_BITS + ZB_POINT_Z_FRAC_BITS));

  v = &c->viewport;

  v->trans.X = ((v->xsize - 0.5) / 2.0) + v->xmin;
  v->trans.Y = ((v->ysize - 0.5) / 2.0) + v->ymin;
  v->trans.Z = ((zsize - 0.5) / 2.0) + ((1 << ZB_POINT_Z_FRAC_BITS)) / 2;

  v->scale.X = (v->xsize - 0.5) / 2.0;
  v->scale.Y = -(v->ysize - 0.5) / 2.0;
  v->scale.Z = -((zsize - 0.5) / 2.0);
}

void glopBegin(GLContext* c, GLParam* p) {
  int type;
  M4 tmp;

  assert(c->in_begin == 0);

  type = p[1].i;
  c->begin_type = type;
  c->in_begin = 1;
  c->vertex_n = 0;
  c->vertex_cnt = 0;

  if (c->matrix_model_projection_updated) {
    if (c->lighting_enabled) {
      /* precompute inverse modelview */
      gl_M4_Inv(&tmp, c->matrix_stack_ptr[0]);
      gl_M4_Transpose(&c->matrix_model_view_inv, &tmp);
    } else {
      float* m = &c->matrix_model_projection.m[0][0];
      /* precompute projection matrix */
      gl_M4_Mul(&c->matrix_model_projection, c->matrix_stack_ptr[1], c->matrix_stack_ptr[0]);
      /* test to accelerate computation */
      c->matrix_model_projection_no_w_transform = 0;
      if (m[12] == 0.0 && m[13] == 0.0 && m[14] == 0.0)
        c->matrix_model_projection_no_w_transform = 1;
    }

    /* test if the texture matrix is not Identity */
    c->apply_texture_matrix = !gl_M4_IsId(c->matrix_stack_ptr[2]);

    c->matrix_model_projection_updated = 0;
  }
  /*  viewport */
  if (c->viewport.updated) {
    gl_eval_viewport(c);
    c->viewport.updated = 0;
  }
  /* triangle drawing functions */
  if (c->render_mode == GL_SELECT) {
    c->draw_triangle_front = gl_draw_triangle_select;
    c->draw_triangle_back = gl_draw_triangle_select;
  } else {
    switch (c->polygon_mode_front) {
      case GL_POINT:
        c->draw_triangle_front = gl_draw_triangle_point;
        break;
      case GL_LINE:
        c->draw_triangle_front = gl_draw_triangle_line;
        break;
      default:
        c->draw_triangle_front = gl_draw_triangle_fill;
        break;
    }

    switch (c->polygon_mode_back) {
      case GL_POINT:
        c->draw_triangle_back = gl_draw_triangle_point;
        break;
      case GL_LINE:
        c->draw_triangle_back = gl_draw_triangle_line;
        break;
      default:
        c->draw_triangle_back = gl_draw_triangle_fill;
        break;
    }
  }
}

/* coords, tranformation , clip code and projection */
/* TODO : handle all cases */
static inline void gl_vertex_transform(GLContext* c, GLVertex* v) {
  float* m;
  V4* n;

  if (c->lighting_enabled) {
    /* eye coordinates needed for lighting */

    m = &c->matrix_stack_ptr[0]->m[0][0];
    v->ec.X = (v->coord.X * m[0] + v->coord.Y * m[1] + v->coord.Z * m[2] + m[3]);
    v->ec.Y = (v->coord.X * m[4] + v->coord.Y * m[5] + v->coord.Z * m[6] + m[7]);
    v->ec.Z = (v->coord.X * m[8] + v->coord.Y * m[9] + v->coord.Z * m[10] + m[11]);
    v->ec.W = (v->coord.X * m[12] + v->coord.Y * m[13] + v->coord.Z * m[14] + m[15]);

    /* projection coordinates */
    m = &c->matrix_stack_ptr[1]->m[0][0];
    v->pc.X = (v->ec.X * m[0] + v->ec.Y * m[1] + v->ec.Z * m[2] + v->ec.W * m[3]);
    v->pc.Y = (v->ec.X * m[4] + v->ec.Y * m[5] + v->ec.Z * m[6] + v->ec.W * m[7]);
    v->pc.Z = (v->ec.X * m[8] + v->ec.Y * m[9] + v->ec.Z * m[10] + v->ec.W * m[11]);
    v->pc.W = (v->ec.X * m[12] + v->ec.Y * m[13] + v->ec.Z * m[14] + v->ec.W * m[15]);

    m = &c->matrix_model_view_inv.m[0][0];
    n = &c->current_normal;

    v->normal.X = (n->X * m[0] + n->Y * m[1] + n->Z * m[2]);
    v->normal.Y = (n->X * m[4] + n->Y * m[5] + n->Z * m[6]);
    v->normal.Z = (n->X * m[8] + n->Y * m[9] + n->Z * m[10]);

    if (c->normalize_enabled) {
      gl_V3_Norm(&v->normal);
    }
  } else {
    /* no eye coordinates needed, no normal */
    /* NOTE: W = 1 is assumed */
    m = &c->matrix_model_projection.m[0][0];

    v->pc.X = (v->coord.X * m[0] + v->coord.Y * m[1] + v->coord.Z * m[2] + m[3]);
    v->pc.Y = (v->coord.X * m[4] + v->coord.Y * m[5] + v->coord.Z * m[6] + m[7]);
    v->pc.Z = (v->coord.X * m[8] + v->coord.Y * m[9] + v->coord.Z * m[10] + m[11]);
    if (c->matrix_model_projection_no_w_transform) {
      v->pc.W = m[15];
    } else {
      v->pc.W = (v->coord.X * m[12] + v->coord.Y * m[13] + v->coord.Z * m[14] + m[15]);
    }
  }

  v->clip_code = gl_clipcode(v->pc.X, v->pc.Y, v->pc.Z, v->pc.W);
}

void glopVertex(GLContext* c, GLParam* p) {
  GLVertex* v;
  int n, i, cnt;

  assert(c->in_begin != 0);

  n = c->vertex_n;
  cnt = c->vertex_cnt;
  cnt++;
  c->vertex_cnt = cnt;

  /* quick fix to avoid crashes on large polygons */
  if (n >= c->vertex_max) {
    GLVertex* newarray;
    c->vertex_max <<= 1; /* just double size */
    newarray = gl_malloc(sizeof(GLVertex) * c->vertex_max);
    if (!newarray) {
      gl_fatal_error("unable to allocate GLVertex array.\n");
    }
    memcpy(newarray, c->vertex, n * sizeof(GLVertex));
    gl_free(c->vertex);
    c->vertex = newarray;
  }
  /* new vertex entry */
  v = &c->vertex[n];
  n++;

  v->coord.X = p[1].f;
  v->coord.Y = p[2].f;
  v->coord.Z = p[3].f;
  v->coord.W = p[4].f;

  gl_vertex_transform(c, v);

  /* color */

  if (c->lighting_enabled) {
    gl_shade_vertex(c, v);
  } else {
    v->color = c->current_color;
  }

  /* tex coords */

  if (c->texture_2d_enabled) {
    if (c->apply_texture_matrix) {
      gl_M4_MulV4(&v->tex_coord, c->matrix_stack_ptr[2], &c->current_tex_coord);
    } else {
      v->tex_coord = c->current_tex_coord;
    }
  }
  /* precompute the mapping to the viewport */
  if (v->clip_code == 0) gl_transform_to_viewport(c, v);

  /* edge flag */

  v->edge_flag = c->current_edge_flag;

  switch (c->begin_type) {
    case GL_POINTS:
      gl_draw_point(c, &c->vertex[0]);
      n = 0;
      break;

    case GL_LINES:
      if (n == 2) {
        gl_draw_line(c, &c->vertex[0], &c->vertex[1]);
        n = 0;
      }
      break;
    case GL_LINE_STRIP:
    case GL_LINE_LOOP:
      if (n == 1) {
        c->vertex[2] = c->vertex[0];
      } else if (n == 2) {
        gl_draw_line(c, &c->vertex[0], &c->vertex[1]);
        c->vertex[0] = c->vertex[1];
        n = 1;
      }
      break;

    case GL_TRIANGLES:
      if (n == 3) {
        gl_draw_triangle(c, &c->vertex[0], &c->vertex[1], &c->vertex[2]);
        n = 0;
      }
      break;
    case GL_TRIANGLE_STRIP:
      if (cnt >= 3) {
        if (n == 3) n = 0;
        /* needed to respect triangle orientation */
        switch (cnt & 1) {
          case 0:
            gl_draw_triangle(c, &c->vertex[2], &c->vertex[1], &c->vertex[0]);
            break;
          default:
          case 1:
            gl_draw_triangle(c, &c->vertex[0], &c->vertex[1], &c->vertex[2]);
            break;
        }
      }
      break;
    case GL_TRIANGLE_FAN:
      if (n == 3) {
        gl_draw_triangle(c, &c->vertex[0], &c->vertex[1], &c->vertex[2]);
        c->vertex[1] = c->vertex[2];
        n = 2;
      }
      break;

    case GL_QUADS:
      if (n == 4) {
        c->vertex[2].edge_flag = 0;
        gl_draw_triangle(c, &c->vertex[0], &c->vertex[1], &c->vertex[2]);
        c->vertex[2].edge_flag = 1;
        c->vertex[0].edge_flag = 0;
        gl_draw_triangle(c, &c->vertex[0], &c->vertex[2], &c->vertex[3]);
        n = 0;
      }
      break;

    case GL_QUAD_STRIP:
      if (n == 4) {
        gl_draw_triangle(c, &c->vertex[0], &c->vertex[1], &c->vertex[2]);
        gl_draw_triangle(c, &c->vertex[1], &c->vertex[3], &c->vertex[2]);
        for (i = 0; i < 2; i++) c->vertex[i] = c->vertex[i + 2];
        n = 2;
      }
      break;
    case GL_POLYGON:
      break;
    default:
      gl_fatal_error("glBegin: type %x not handled\n", c->begin_type);
  }

  c->vertex_n = n;
}

void glopEnd(GLContext* c, GLParam* param) {
  assert(c->in_begin == 1);

  if (c->begin_type == GL_LINE_LOOP) {
    if (c->vertex_cnt >= 3) {
      gl_draw_line(c, &c->vertex[0], &c->vertex[2]);
    }
  } else if (c->begin_type == GL_POLYGON) {
    int i = c->vertex_cnt;
    while (i >= 3) {
      i--;
      gl_draw_triangle(c, &c->vertex[i], &c->vertex[0], &c->vertex[i - 1]);
    }
  }
  c->in_begin = 0;
}
